package cn.wgx.security.config;

import cn.wgx.security.config.granter.SMSCodeTokenGranter;
import cn.wgx.security.config.tokenstore.ResJwtAccessTokenConverter;
import cn.wgx.security.service.ValidateCodeService;
import cn.wgx.security.config.granter.PasswordEnhanceTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;


/**
 * spring oauth2配置, 基于jdbc保存客户端信息,适合大量客户端
 * 需在数据库先存储客户端信息, id和秘钥
 * <p>
 * 文档: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
 */
@Configuration
@EnableAuthorizationServer
@Order(2)
public class Oauth2ServerJDBCConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private DataSource dataSource;

    @Resource
    private TokenStore tokenStore;

    @Autowired
    ValidateCodeService validateCodeService;

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 配置客户端信息(资源服务器)
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 读取客户端配置
        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    /**
     * 配置令牌
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 设置令牌模式
        //客户端模式   GRANT_TYPE = "client_credentials";
        //密码模式	  GRANT_TYPE = "password";
        //授权码模式   GRANT_TYPE = "authorization_code";
        //刷新模式	  GRANT_TYPE = "refresh_token";
        //简易模式	  GRANT_TYPE = "implicit";
        //短信模式	  GRANT_TYPE = "sms"; 参考ResourceOwnerPasswordTokenGranter重写

        //存储token容器
        endpoints.tokenStore(tokenStore)
                //配置密码模式
                .authenticationManager(authenticationManager)
                //用户管理
                .userDetailsService(userDetailsService)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);

        //处理oauth 模式
        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
        //设定为只判断user的权限范围,不判断client的范围
        if(requestFactory instanceof DefaultOAuth2RequestFactory){
            ((DefaultOAuth2RequestFactory) requestFactory).setCheckUserScopes(true);
        }

        List<TokenGranter> tokenGranters = new ArrayList<>();
        //oauth2登录之 -- 密码登录模式 GRANT_TYPE = "password";  参考spring的实现代码 ResourceOwnerPasswordTokenGranter重写
        tokenGranters.add(new PasswordEnhanceTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory, validateCodeService));
        //客户端模式   GRANT_TYPE = "client_credentials";
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
        //授权码模式   GRANT_TYPE = "authorization_code";
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, requestFactory));
        //刷新模式	  GRANT_TYPE = "refresh_token";
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        //简易模式	  GRANT_TYPE = "implicit";
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory));
        //短信模式	  GRANT_TYPE = "sms"; 参考ResourceOwnerPasswordTokenGranter重写
        tokenGranters.add(new SMSCodeTokenGranter(userDetailsService, validateCodeService, tokenServices, clientDetails, requestFactory));
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
    }

    /**
     * 安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)  {
                //配置请求权限 /oauth/token_key 访问权限 只允许验证过的请求获取TokenAccess
        security.tokenKeyAccess("isAuthenticated()")
                //配置请求权限  /oauth/check_token 访问权限 允许所有请求检测TokenAccess
                .checkTokenAccess("permitAll()")
                //允许  /oauth/token支持client_id以及client_secret作登录认证
                .allowFormAuthenticationForClients();

        //官方示例
//        security.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
//                .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");

    }

//------------ 以下为tokenStore配置,根据配置文件自动选择其中一项

    /**
     * 数据库存储token信息
     *
     * @param dataSource
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "security.oauth2.token.store", name = "type", havingValue = "jdbc", matchIfMissing = false)
    public JdbcTokenStore jdbcTokenStore(DataSource dataSource) {
//		oauth_access_token, oauth_refresh_token 创建两张表
        return new JdbcTokenStore(dataSource);

    }

    /**
     * redis存储token信息
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    @ConditionalOnProperty(prefix = "security.oauth2.token.store", name = "type", havingValue = "redis", matchIfMissing = true)
    public RedisTokenStore redisTokenStore(RedisConnectionFactory connectionFactory) {
//		return new RedisTokenStore( redisTemplate.getConnectionFactory() ) ; //单台redis服务器
        Assert.state(connectionFactory != null, "connectionFactory must be provided");
        RedisTokenStore redisTemplateStore = new RedisTokenStore(connectionFactory);
        return redisTemplateStore;
    }

    //使用jwt替换原有的uuid生成token方式
    @Configuration
    @ConditionalOnProperty(prefix = "security.oauth2.token.store", name = "type", havingValue = "jwt", matchIfMissing = false)
    public static class JWTTokenConfig {
        @Bean
        public JwtTokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }

        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter accessTokenConverter = new ResJwtAccessTokenConverter();
            accessTokenConverter.setSigningKey("ocp");
            return accessTokenConverter;
        }
    }


}
